import numpy as np

#定义朴素贝叶斯的基类
class NaiveBayes(object):

    """
    初始化结构
    _x, _y: 记录训练集的变量
    _data: 核心数组存储实际使用概率的相关信息
    _func: 决策函数,根据输入的x,y输出对应的后验概率
    _n_possibililties: 记录各个纬度特征取值个数的数组
    _labelled_x: 记录安类别分开后的输入数据的数组
    _label_zip: 记录类别相关的数组,视具体算法,定义有所不同
    _cat_counter: 核心数组,记录第i类数据的个数 (cat是category的缩写)
    _con_counter: 核心数组,用于记录数据条件概率的原始极大似然估计
    label_dic: 核心字典,用于记录数值化类别时的转换关系
    _feat_dics: 核心字典,用于记录数值化各纬度特征(feat)的转换关系
    """
    def __init__(self):
        self._x = self._y = None
        self._data = self._func = None
        self._n_possibilities = None
        self._labelled_x = self._label_zip = None
        self._cat_counter = self._con_counter = None
        self.label_dic = self._feat_dics = None

    #重载__getitem__ 运算符以避免定义大量property
    def __getitem__(self, item):
        if isinstance(item, str):
            return getattr(self, "_" + item)

    #留下抽象方法让子类定义,这里的tar_idx参数和self._tar_idx的意义一致
    def feed_data(self, x, y, sample_weight=None):
        pass

    #留下抽象方法让子类定义,这里的smaple_weight参数代表样本权重
    def feed_sample_weight(self, sample_weight=None):
        pass

    #定义计算先验概率的函数
    #lb的默认值是1,也就是说默认采取拉普斯平滑(下同)
    def get_prior_probability(self, lb=1):
        return [(_c_num + lb) / (len(self._y) + lb * len(self._cat_counter))
                for _c_num in self._cat_counter]

    def fit(self, x=None, y=None, sample_weight=None, lb=1):
        #
        if x is not None and y is not None:
            self.feed_data(x, y, sample_weight)
        # 调用核心算法得到决策函数
        self._func = self._fit(lb)

    def _fit(self, lb):
        pass

    def predict_one(self, x, get_raw_result=False):
        """
        定义预测单一样本的函数
        :param x:
        :param get_raw_result: 控制该函数是输出预测的类型还是输出相应的后验证率(False:输出类别, Ture:输出后验证率)
        :return:
        """
        #在预测之前先将新的输入数据数值化,如果是numpy数组,则转换为python数组(后者操作更快),否则进行拷贝
        if isinstance(x, np.ndarray):
            x = x.tolist()
        else:
            x = x[:]
        # 调用相关方法进行数值化,该方法随具体模型的不同而不同
        x = self._transfer_x(x)
        m_arg, m_probability = 0, 0
        # 遍历各类型,找到能使用后验概率最大的类别
        for i in range(len(self._cat_counter)):
            p = self._func(x, i)
            if p > m_probability:
                m_arg, m_probability = i, p
        if not get_raw_result:
            return self.label_dic[m_arg]
        return m_probability

    # 定义预测多样本的函数
    def predict(self, x, get_raw_result=False):
        return np.array([self.predict_one(xx ,get_raw_result)
                         for xx in x])

    # 定义能对新数据进行评估的方法,暂以简单输出准确率为掩饰
    def evaluate(self, x, y):
        y_pred = self.predict(x)
        print("Acc: {:12.6} %".format(100 * np.sum(y_pred == y) / len(y)))